跳到主要内容

Java 集合类学习 通用接口

什么是集合框架

参考资料 Java 集合框架

早在 Java 2 中之前,Java 就提供了特设类。比如:Dictionary、Vector、Stack 和 Properties 这些类用来存储和操作对象组。

虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由于这个原因,使用 Vector 类的方式和使用 Properties 类的方式有着很大不同。因此集合框架被设计成要满足以下几个目标。

  1. 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
  2. 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。
  3. 对一个集合的扩展和适应必须是简单的。

为此,整个集合框架就围绕一组标准接口而设计。可以直接使用这些接口的标准实现,诸如: LinkedList,HashSet,和 TreeSet 等,除此之外也可以通过这些接口实现自己的集合。

2243690-9cd9c896e0d512ed.gif

Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。

Collection 集合接口

Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java 不提供直接继承自 Collection 的类,只提供继承于的子接口(如List和set)。

Collection 接口的常见方法

这个接口它主要提供如下的成员方法

方法描述
boolean add(Object obj)添加到集合成功返回true。
boolean addAll(Collection c)增加了C的所有元素到调用集合。
void clear( )移除调用集合中的所有元素。
boolean contains(Object obj)检查是否包含
boolean containsAll(Collection c)如果调用集合中包含c的所有元素返回true。
boolean equals(Object obj)检查元素是否相等
int hashCode( )返回调用集合的哈希代码。
boolean isEmpty( )如果调用集合为空返回true。否则,返回false。
Iterator iterator( )返回一个迭代器的调用集合。
boolean remove(Object obj)从调用集合中移除一个实例obj。
boolean removeAll(Collection c)删除从调用集合c中的所有元素。
boolean retainAll(Collection c)移除除了 c 的所有元素
int size( )集合元素的数量
Object[ ] toArray( )返回所有的元素到一个数组里
Object[ ] toArray(Object array[ ])返回一个与传入元素匹配的数组

这个 Collection 主要的子接口有 List、Set、SortedSet 这三种

迭代器的使用(Iterator)

参考资料 使用Iterator

Collection 内部实现了迭代器模式,所有一般要操作集合内的元素,例如迭代时删除这种操作就可以使用迭代器,甚至 foreach 都是基于迭代器的

List<String> list = List.of("Apple", "Orange", "Pear");
for (String s : list) {
System.out.println(s);
}

实际上,Java 编译器并不知道如何遍历 List。上述代码能够编译通过,只是因为编译器把 foreach 循环通过 Iterator 改写为了普通的 for 循环:

for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}

使用迭代器

平时一般无需显示的使用迭代器,只有要在遍历的时候删除元素才需要用迭代器

List<Student> list = new ArrayList<>();
list.add(new Student("male"));
list.add(new Student("female"));
list.add(new Student("female"));
list.add(new Student("male"));

//遍历删除,除去男生
Iterator<Student> iterator = list.iterator();
while (iterator.hasNext()) {
Student student = iterator.next();
if ("male".equals(student.getGender())) {
iterator.remove();//使用迭代器的删除方法删除
}
}

如果将上例中的 iterator.remove(); 改为 list.remove(student); 将会报 ConcurrentModificationException 异常。(如下这样)

for(int i=0; i< list.size(); i++){
if(list.get(i).equals("del"))
list.remove(i);
}
// 或者
for(Integer i : list){
if(i.equals("del"))
list.remove(i);
}

这是因为:使用迭代器遍历,却使用集合的方法删除元素的结果。

Iterable 接口实现迭代器模式

要实现自己的 List 并且能够使用迭代器则必须实现这个 Iterable 接口

public class Main {
public static void main(String[] args) {
ReverseList<String> list = new ReverseList<>();
list.add("Apple");
list.add("Orange");
list.add("Pear");
for (String s : list) {
System.out.println(s);
}
}
}

class ReverseList<T> implements Iterable<T> {

private List<T> list = new ArrayList<>();

public void add(T t) {
list.add(t);
}

@Override
public Iterator<T> iterator() {
return new ReverseIterator(list.size());
}

class ReverseIterator implements Iterator<T> {
int index;

ReverseIterator(int index) {
this.index = index;
}

@Override
public boolean hasNext() {
return index > 0;
}

@Override
public T next() {
index--;
return ReverseList.this.list.get(index);
}
}
}

对象的比较

在实际应用中,往往有需要比较两个自定义对象大小的地方。而这些自定义对象的比较,就不像简单的整型数据那么简单,它们往往包含有许多的属性,我们一般都是根据这些属性对自定义对象进行比较的。所以 Java 中要比较对象的大小或者要对对象的集合进行排序,需要通过比较这些对象的某些属性的大小来确定它们之间的大小关系。

一般,Java 中通过接口实现两个对象的比较,比较常用就是 Comparable 接口和 Comparator 接口,两者虽然都是用于比较的,但是使用上还是有一定的区别,前者是赋予对象一个能比较的功能,一般用于接口编程(像 TreeSet 就必须要对象实现这个接口)。后者则一般用于函数式编程,这个接口上还标识了 @FunctionalInterface

Comparable 可以让实现它的类的对象进行比较,具体的比较规则是按照 compareTo 方法中的规则进行。这种顺序称为 自然顺序

compareTo 方法的返回值有三种情况:

e1.compareTo(e2) > 0 即 e1 > e2
e1.compareTo(e2) = 0 即 e1 = e2
e1.compareTo(e2) < 0 即 e1 < e2

Comparator 用于定制排序

Comparable 接口

定义一个对象,对象去实现 Comparable 接口,里面只有一个 compareTo 方法

例如:obj1.compareTo(obj2) 方法如果返回 0,则说明被比较的两个对象相等,如果返回一个正数,则表明 obj1 大于 obj2,如果是 负数,则表明 obj1 小于 obj2。

说白了继承这个接口后无需使用定制排序,直接默认调用排序接口就行了

public class Temp implements Comparable<Temp> {
private int age;
private String name;

public Temp(int age, String name) {
this.age = age;
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Temp temp = (Temp) o;
return age == temp.age &&
Objects.equals(name, temp.name);
}

@Override
public int hashCode() {
return Objects.hash(age, name);
}

@Override
public int compareTo(Temp o) {
return this.age - o.age;
}
}

然后就可以在 TreeSet 里面使用它了

public static void main(String[] args) {
// 自然排序
TreeSet<Temp> set = new TreeSet<>();
set.add(new Temp(18, "张三"));
set.add(new Temp(14, "王二"));
set.add(new Temp(12, "赵六"));
set.forEach(System.out::println);
}

Comparator 接口

这个则是用于函数式编程,还是用上面那个对象

public static void main(String[] args) {
// 这里可以直接使用 Lambda
// TreeSet<Temp> set2 = new TreeSet<>((o1, o2) -> o1.age - o2.age);
TreeSet<Temp> set2 = new TreeSet<>(new Comparator<Temp>() {
@Override
public int compare(Temp o1, Temp o2) {
return o1.age - o2.age;
}
});

set2.add(new Temp(18, "张三"));
set2.add(new Temp(14, "王二"));
set2.add(new Temp(12, "赵六"));
set2.forEach(System.out::println);
}

这个 Comparator 接口内部就一个 compare 方法必须实现

总结

Java 中的两种排序方式:

  • Comparable 自然排序。(实体类实现)
  • Comparator 是定制排序。(无法修改实体类时,直接在调用方创建)

说白了就是 Comparable 的这个接口实现后,直接调用 sort 方法就能直接排序了,无需再手动定制排序的方法,而 Comparator 是定制排序。

对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了 Comparable 接口,实现了 compareTo 方法,我们可以直接使用。

而对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建 Comparator 接口,然后使用特定的 Comparator 实现进行比较。